#include <ak_ao.h>
#include <ak_ai.h>
#include <ak_thread.h>
#include <ak_mem.h>
#include <wl_file.h>
#include "wl_audio.h"

#include "wl_malloc.h"
#include "wl_mutex.h"
#include "wl_trace.h"
#include "wl_utils.h"
#include "ringbuffer.h"

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

typedef struct __wl_audio_recorder {
    wl_audio_recorder_callback callback;
    wl_audio_end_callback      end_callback;
    int handle;
    wl_bool is_recording;
    ak_pthread_t record_thread;
} wl_audio_recorder;

typedef struct __wl_audio_player {
    wl_audio_player_callback callback;
    wl_audio_end_callback    end_callback;
    int handle;
    ak_pthread_t play_thread;
    WL_MUTEX play_mutex;
    ak_cond_t play_cond;
    struct ring_buffer_t *p_rb;
    wl_bool is_playing;
    int play_frame_size;
    WL_FHANDLE fd;
} wl_audio_player;

typedef struct __wl_audio_global {
    wl_bool           initialized;
    wl_audio_recorder recorder;
    wl_audio_player   player;
    WL_MUTEX          mutex;
} wl_audio_global;

static wl_audio_global s_audio_global;




wl_int32 wl_audio_init(void) {
    WLLOGW("audio init");
    if (!s_audio_global.initialized) {
        if (0 != wl_mutex_create("audio", &s_audio_global.mutex)) {
            WLLOGW("wl_mutex_create fail");
            return -1;
        }

        s_audio_global.initialized = wl_true;
    }
    WLLOGW("audio init success");
    return 0;
}

wl_int32 wl_audio_end(void) {
    if (s_audio_global.initialized) {
        wl_mutex_delete(&s_audio_global.mutex);
        s_audio_global.initialized = wl_false;
    }
    return 0;
}


void* wl_audio_record_task_entry(void *p_param) {
    wl_audio_recorder* recorder = (wl_audio_recorder *)p_param;
    struct frame pcm_frame;
    int rc = 0;
    ak_thread_set_name("record task");
    ak_ai_start_capture(recorder->handle);

    while(recorder->is_recording) {
        rc = ak_ai_get_frame(recorder->handle, &pcm_frame, 1);
        if (rc == 0) {
            if (recorder->callback) {
                recorder->callback(recorder, pcm_frame.data, pcm_frame.len);
            }

            ak_ai_release_frame(recorder->handle, &pcm_frame);
        }if (rc != ERROR_AI_CAPTURE_NO_START && rc != ERROR_AI_NO_DATA) {
            break;
        }
    }

    ak_ai_stop_capture(recorder->handle);
    if (recorder->is_recording) {
        if (recorder->end_callback) {
            recorder->end_callback(recorder, rc);
        }

        ak_ai_close(recorder->handle);
        recorder->handle = 0;
    }

    return wl_null;
}

WL_AUDIO_HANDLE wl_audio_start_record(wl_audio_recorder_callback callback, wl_audio_end_callback end_callback) {
    wl_audio_recorder* recorder = &s_audio_global.recorder;
    memset(recorder, 0, sizeof(wl_audio_recorder));
    recorder->callback = callback;
    recorder->end_callback = end_callback;

    struct ak_audio_in_param param;
    param.dev_id = 1;
    param.pcm_data_attr.channel_num = AUDIO_CHANNEL_MONO;
    param.pcm_data_attr.sample_bits = AK_AUDIO_SMPLE_BIT_16;
    param.pcm_data_attr.sample_rate = RECORD_PCM_RATE;
    int rc = ak_ai_open(&param, &recorder->handle);
    if (rc == 0) {
        ak_ai_set_source(recorder->handle, AI_SOURCE_MIC);
        ak_ai_set_frame_length(recorder->handle, RECORD_PCM_FRAME_BYTES);
        recorder->is_recording = wl_true;
        if (0 != ak_thread_create(&recorder->record_thread, wl_audio_record_task_entry, recorder, ANYKA_THREAD_MIN_STACK_SIZE, -1)) {
            ak_ai_close(recorder->handle);
            memset(recorder, 0, sizeof(wl_audio_recorder));
            return wl_null;
        }


        return recorder;
    }

    return wl_null;
}

wl_int32 wl_audio_stop_record(WL_AUDIO_HANDLE handle) {
    if (handle) {
        wl_audio_recorder* recorder = (wl_audio_recorder*)handle;
        if (recorder->is_recording) {
            recorder->is_recording = wl_false;
            ak_thread_join(recorder->record_thread);
            ak_ai_close(recorder->handle);
            if (recorder->end_callback) {
                recorder->end_callback(recorder, 0);
            }
        }

        memset(recorder, 0, sizeof(wl_audio_recorder));
    }

    return 0;
}

void* wl_audio_play_task_entry(void *p_param) {
    wl_audio_player *player = (wl_audio_player *)p_param;
    int rb_size = 0;
    int read_ptr_id = 0;
    int read_result = 0;
    int play_pcm_len = 0;
    int rc = 0;

    wl_uint8 *data = (wl_uint8 *)wl_malloc(player->play_frame_size);
    if (data != wl_null) {
        ak_thread_set_name("player task");
        while(player->is_playing) {
            rb_size = ring_buffer_num_items(player->p_rb);
            if (rb_size >= player->play_frame_size) {
                read_result = ring_buffer_dequeue_arr(player->p_rb, (char *)data, player->play_frame_size);
                if (0 != ak_ao_send_frame(player->handle, data, read_result, &play_pcm_len)) {
                    WLLOGD("play fail!!!\n");
                }
            }else {
//                struct timespec timeout;
//                timeout.tv_nsec = 10000000;
                ak_thread_mutex_lock(&player->play_mutex);
                if (player->callback) {
                    player->callback(player);
                }
                rc = ak_thread_cond_wait(&player->play_cond, &player->play_mutex);
                ak_thread_mutex_unlock(&player->play_mutex);

                if (!player->is_playing) {
                    rb_size = ring_buffer_num_items(player->p_rb);
                    if (rb_size >= player->play_frame_size) {
                        read_result = ring_buffer_dequeue_arr(player->p_rb, (char *)data, player->play_frame_size);
                        if (0 != ak_ao_send_frame(player->handle, data, read_result, &play_pcm_len)) {
                            WLLOGD("play fail!!!\n");
                        }
                    }
                }
            }
        }

        wl_free(data);
        WLLOGD("play end of thread.");
        ak_ao_wait_play_finish(player->handle);
        WLLOGD("after play finish");
    }

    return wl_null;
}

WL_AUDIO_HANDLE wl_audio_start_play(wl_audio_player_callback callback, wl_audio_end_callback end_callback) {
    int rc = 0;
    wl_audio_player* player = &s_audio_global.player;
    memset(player, 0, sizeof(wl_audio_player));
    player->callback = callback;
    player->end_callback = end_callback;

    struct ak_audio_out_param param;
    param.dev_id = 0;
    param.pcm_data_attr.sample_rate = PLAY_PCM_RATE;
    param.pcm_data_attr.sample_bits = AK_AUDIO_SMPLE_BIT_16;
    param.pcm_data_attr.channel_num = AUDIO_CHANNEL_MONO;

    switch (param.pcm_data_attr.sample_rate) {
        case AK_AUDIO_SAMPLE_RATE_8000:
        case AK_AUDIO_SAMPLE_RATE_12000:
        case AK_AUDIO_SAMPLE_RATE_11025:
        case AK_AUDIO_SAMPLE_RATE_16000:
        case AK_AUDIO_SAMPLE_RATE_22050:
        case AK_AUDIO_SAMPLE_RATE_24000:
            player->play_frame_size = 512;
            break;
        case AK_AUDIO_SAMPLE_RATE_32000:
        case AK_AUDIO_SAMPLE_RATE_44100:
            player->play_frame_size = 1024;
            break;
        case AK_AUDIO_SAMPLE_RATE_48000:
            player->play_frame_size = 2048;
            break;
        case AK_AUDIO_SAMPLE_RATE_64000:
            player->play_frame_size = 4096;
            break;
    }

    player->play_frame_size = PLAY_PCM_FRAME_BYTES;

    if ((rc = ak_ao_open(&param, &player->handle)) == 0) {
        player->p_rb = (struct ring_buffer_t *)wl_malloc(sizeof(struct ring_buffer_t));
        if (wl_null == player->p_rb) {
            ak_ao_close(player->handle);
            memset(player, 0, sizeof(wl_audio_player));
            return wl_null;
        }

        if (0 != wl_mutex_create("play mutex", &player->play_mutex)) {
            wl_free(player->p_rb);
            ak_ao_close(player->handle);
            memset(player, 0, sizeof(wl_audio_player));
            return wl_null;
        }

        if (0 != ak_thread_cond_init(&player->play_cond)) {
            wl_mutex_delete(&player->play_mutex);
            wl_free(player->p_rb);
            ak_ao_close(player->handle);
            memset(player, 0, sizeof(wl_audio_player));
            return wl_null;
        }

        player->is_playing = wl_true;
        if (0 != ak_thread_create(&player->play_thread, wl_audio_play_task_entry, player, ANYKA_THREAD_MIN_STACK_SIZE, 20)) {
            ak_thread_cond_destroy(&player->play_cond);
            wl_mutex_delete(&player->play_mutex);
            wl_free(player->p_rb);
            ak_ao_close(player->handle);
            memset(player, 0, sizeof(wl_audio_player));
            return wl_null;
        }

        ak_ao_set_volume(player->handle, 0);
        ak_ao_set_speaker(player->handle, 1);

        return player;
    }

    WLLOGD("open play result:%d\n", rc);
    return wl_null;
}

wl_int32 wl_audio_put_play_data(WL_AUDIO_HANDLE handle, wl_uint8* data, wl_uint32 data_size) {
    wl_audio_player* player = (wl_audio_player *)handle;
    wl_bool need_signal = wl_false;

    if (player && player->is_playing) {
        int rb_size = 0;
        rb_size = ring_buffer_num_items(player->p_rb);
        if (rb_size < player->play_frame_size) {
            need_signal = wl_true;
        }

        ring_buffer_queue_arr(player->p_rb, (const char *)data, data_size);
        if (data_size + rb_size >= player->play_frame_size && need_signal) {
            ak_thread_mutex_lock(&player->play_mutex);
            ak_thread_cond_signal(&player->play_cond);
            ak_thread_mutex_unlock(&player->play_mutex);
        }
    }

    return 0;
}

wl_int32 wl_audio_stop_play(WL_AUDIO_HANDLE handle) {
    wl_audio_player* player = (wl_audio_player*)handle;
    if (player && player->is_playing) {
        wl_fclose(player->fd);
        player->fd = 0;
        player->is_playing = wl_false;
        ak_thread_cond_signal(&player->play_cond);
        WLLOGD("waiting for thread");
        ak_thread_join(player->play_thread);
        WLLOGD("finish for thread");
        ak_ao_close(player->handle);

        ak_thread_cond_destroy(&player->play_cond);
        wl_mutex_delete(&player->play_mutex);
        wl_free(player->p_rb);

        if (player->end_callback) {
            player->end_callback(player, 0);
        }

        memset(player, 0, sizeof(wl_audio_player));
    }
    return 0;
}


#ifdef __cplusplus
}
#endif /* __cplusplus */
